package cn.cosmosx.api.framework;

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * HttpClientServiceFactory的BeanDefinition注册
 *
 * @author geng
 */
@Slf4j
@Component
public class ApiServiceInterfaceBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware, ResourceLoaderAware {
    /**
     * 接口包扫描配置文件属性前缀
     */
    private static final String DEFAULT_FIX_API_SERVICE_INTERFACE_PACKAGES_PREFIX = "qs.api-service-interface-packages";
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private ApplicationContext applicationContext;
    private ResourcePatternResolver resourcePatternResolver;
    private MetadataReaderFactory metadataReaderFactory;
    /**
     * 接口集合
     */
    private Set<Class<?>> apiInterfaceClassSet;

    @Value("${}")
    private String apiServiceInterfacePackages;

    /**
     * 使用Spring后置处理器注册bean定义
     *
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(@NotNull BeanDefinitionRegistry registry) throws BeansException {
        //这里一般我们是通过反射获取需要代理的接口的clazz列表
        //todo 比如判断包下面的类，或者通过某注解标注的类等等
        String[] packages = getPackages();
        if (packages.length == 0) {
            log.warn("Not any API Service Interface to register to SpringContext Container.");
            log.warn("If need to config interface to SpringContext, Please config scanner packages Attribute to profile: qs.api-service-interface-packages.");
            return;
        }
        // 包扫描
        Set<Class<?>> beanClazzSet = new LinkedHashSet<>();
        for (String pkg : packages) {
            beanClazzSet.addAll(scannerPackages(StringUtils.trimAllWhitespace(pkg)));
        }
        // 注册代理Bean，偷梁换柱。将原接口类换成ApiServiceFactoryBean代理类
        for (Class<?> beanClazz : beanClazzSet) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

            //在这里，我们可以给该对象的属性注入对应的实例。
            //比如mybatis，就在这里注入了dataSource和sqlSessionFactory，
            // 注意，如果采用definition.getPropertyValues()方式的话，
            // 类似definition.getPropertyValues().add("interfaceType", beanClazz);
            // 则要求在FactoryBean（本应用中即ServiceFactory）提供setter方法，否则会注入失败
            // 如果采用definition.getConstructorArgumentValues()，
            // 则FactoryBean中需要提供包含该属性的构造方法，否则会注入失败
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

            //注意，这里的BeanClass是生成Bean实例的工厂，不是Bean本身。
            // FactoryBean是一种特殊的Bean，其返回的对象不是指定类的一个实例，
            // 其返回的是该工厂Bean的getObject方法所返回的对象。
            definition.setBeanClass(ApiServiceFactoryBean.class);

            //这里采用的是byType方式注入，类似的还有byName等
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
            log.info("Registered API Service Interface Proxy Bean:{}", beanClazz.getName());
        }
    }

    /**
     * 根据包路径获取包及子包下的所有类
     *
     * @param basePackage basePackage
     * @return Set<Class < ?>> Set<Class<?>>
     */
    private Set<Class<?>> scannerPackages(String basePackage) {
        Set<Class<?>> set = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
        try {
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> clazz;
                    try {
                        clazz = Class.forName(className);
                        if (clazz.isEnum() || clazz.isAnonymousClass() || clazz.isMemberClass() || clazz.isPrimitive() || clazz.isArray()) {
                            continue;
                        }
                        if (Modifier.isPublic(clazz.getModifiers()) && clazz.isInterface()) {
                            set.add(clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        log.error("Api Service Interface scanner Error", e);
                    }
                }
            }
        } catch (IOException e) {
            log.error("Api Service Interface scanner Error", e);
        }
        return set;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    private Environment getEnvironment() {
        return applicationContext.getEnvironment();
    }

    /**
     * 获得包扫描属性配置
     * @return
     */
    private String[] getPackages() {
        return getEnvironment().getRequiredProperty(DEFAULT_FIX_API_SERVICE_INTERFACE_PACKAGES_PREFIX, String[].class);
    }

    @Override
    public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // do nothing
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 资源加载器
     *
     * @param resourceLoader -资源加载器
     */
    @Override
    public void setResourceLoader(@NotNull ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }
}
